package org.jcc.core.demo.cash;

import java.util.Arrays;

/**
 * 零钱的枚举
 * 通常用int即可表示，这里对int进行了简单封装
 * 一则限制了它的可能取值范围
 * 二则引入了int（Integer）中所没有的方法，同时也限制了它只能在Cash中调用
 * 
 * @author Golden Shaw
 *
 */
public enum Cash {
    CASH_100(100), CASH_50(50), CASH_20(20), CASH_10(10), CASH_5(5), CASH_1(1);

    private int value;
    
    private Cash(int value) {
        this.value = value;
    }
    
    // ================ 第二种方式，更加对象化，把第一种方式中的int类型的genericCash包装成类
    public int getCountOfChanges(final CashList list) {
        /**
         * 一般化的现金值，在运算中，我们会逐步分割要换的零钱，这会导致出现一些中间值
         * 举个例子，把100换成[20, 10, 5]，在运算中将出现把100分成80+20的情况，然后将计算把“80”换成[20,10,5]的情况
         * 这里的80就是一般化的现金值，在实际中不会存在，但在我们的运算中会有意义。
         * 
         * 这是一个方法内部类，仅仅只用于也只能用于这个方法中。
         *
         */
        class GenericCash {
            private int value;
            
            private GenericCash() {
                // 由于是内部类，可以直接引用所在类的私有成员变量
                value = Cash.this.value;
            }
            
            // 供递归调用时使用的构造函数
            private GenericCash(int value) {
                this.value = value;
            }
            
            private boolean canBeChangedWith(Cash cash) {
                // 由于是内部类，可以直接引用cash的私有成员变量
                return value % cash.value == 0;
            }

            private boolean isNegative() {
                return value < 0;
            }
            
            private GenericCash subtractBy(Cash cash) {
                return new GenericCash(value - cash.value);
            }

            private boolean isZero() {
                return value == 0;
            }
            
            private int getCountOfChanges() {
                // 由于是内部类，这里直接引用外围方法的list变量，这也是它为何要设置成final的原因
                return caseOfChanges(this, list);
            }
            
            private int caseOfChanges(GenericCash genericCash, CashList list) {
                if (genericCash.isNegative()) {
                    return 0;
                }
                if (genericCash.isZero()) {
                    return 1;
                }
                if (list.containsOnlyOneCash()) {
                    return genericCash.canBeChangedWith(list.getTheOnlyOneCash()) ? 1 : 0;
                }
                return caseOfChanges(genericCash.subtractBy(list.getFirstCash()), list)
                        + caseOfChanges(genericCash, list.newCashListWithoutFirstCash());
            }
        }
        
        // 由于内部类的关系，在这里完全不用向里面传递任何参数
        return new GenericCash().getCountOfChanges();
    }
    
    /**
     * 求零钱种类（静态方式，使用数组拷贝）
     * @param cash
     * @param cashes
     * @return
     */
    public static int caseOfChanges(int cash, int[] cashes) {
        if (cash < 0) {
            return 0;
        }
        if (cash == 0) {
            return 1;
        }
        if (cashes.length == 1) {
            return cash % cashes[0] == 0 ? 1 : 0;
        }
        // 递归调用
        return caseOfChanges(cash - cashes[0], cashes)
                + caseOfChanges(cash, Arrays.copyOfRange(cashes, 1, cashes.length));
    }
    
    /**
     * 求零钱种类（静态方式，不使用数组拷贝， 增加第三个参数 index 的方式）
     * @param cash
     * @param cashes
     * @param index
     * @return
     */
    public static int caseOfChanges2(int cash, int[] cashes, int index) {
        if (cash < 0) {
            return 0;
        }
        if (cash == 0) {
            return 1;
        }
        if (cashes.length == index + 1) {
            return cash % cashes[index] == 0 ? 1 : 0;
        }
        // 递归调用
        return caseOfChanges2(cash - cashes[index], cashes, index)
                + caseOfChanges2(cash, cashes, ++index);
    }
    /**
     * 求零钱种类（静态方式，不使用数组拷贝， 增加第三个参数 index 的方式）
     * 简化整除的 case，实际上整除的 case 与 cash == 0 的 case 是等价的，
     * 因为只要能整除，不断约简下去必然出现 cash == 0 的情况（不过这样也会需要更多的运算）
     * 另外要增加对 index 溢出的判断。
     * @param cash
     * @param cashes
     * @param index
     * @return
     */
    public static int caseOfChanges3(int cash, int[] cashes, int index) {
    	if (cash < 0 || index >= cashes.length) {
    		return 0;
    	} else if (cash == 0) {
    		return 1;
    	} else {
    		// 递归调用
    		return caseOfChanges3(cash - cashes[index], cashes, index)
    				+ caseOfChanges3(cash, cashes, ++index);
    	}
    }


}
